package ru.volgogradetzzz.protocol.objects
{
	import flash.utils.ByteArray;
	import flash.utils.describeType;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	import ru.volgogradetzzz.protocol.types.byte;
	import ru.volgogradetzzz.protocol.types.double;
	import ru.volgogradetzzz.protocol.types.float;
	import ru.volgogradetzzz.protocol.types.short;
	import ru.volgogradetzzz.protocol.types.ubyte;
	import ru.volgogradetzzz.protocol.types.ushort;
	/**
	 * ...
	 * @author volgogradetzzz
	 */
	public class SerializableObject 
	{
		/*
		'Boolean'
		'ru.volgogradetzzz.protocol.types::byte'
		'ru.volgogradetzzz.protocol.types::ubyte'
		'ru.volgogradetzzz.protocol.types::double'
		'ru.volgogradetzzz.protocol.types::float'
		'int'
		'ru.volgogradetzzz.protocol.types::short'
		'ru.volgogradetzzz.protocol.types::ushort'
		'String'
		'uint'
		
		'__AS3__.vec::Vector.<Boolean>'
		'__AS3__.vec::Vector.<ru.volgogradetzzz.protocol.types::byte>'
		'__AS3__.vec::Vector.<ru.volgogradetzzz.protocol.types::ubyte>'
		'__AS3__.vec::Vector.<ru.volgogradetzzz.protocol.types::double>'
		'__AS3__.vec::Vector.<ru.volgogradetzzz.protocol.types::float>'
		'__AS3__.vec::Vector.<int>'
		'__AS3__.vec::Vector.<ru.volgogradetzzz.protocol.types::short>'
		'__AS3__.vec::Vector.<ru.volgogradetzzz.protocol.types::ushort>'
		'__AS3__.vec::Vector.<String>'
		'__AS3__.vec::Vector.<uint>'
		*/
		
		private const _SERIALIZABLE_FIELDS:Vector.<SerializableField> = new Vector.<SerializableField>();
		private const _BYTE_ARRAY:ByteArray = new ByteArray();
		private const VECTOR_PATTERN:RegExp = /__AS3__.vec::Vector.*/;
		private const VECTOR_TYPE_PATTERN:RegExp = /<(.*)>/;
		
		public function SerializableObject()
		{
			var xml:XML = describeType(this);
			
			var vars:XMLList = xml.variable;
			for each (var item:XML in vars) 
			{
				for each (var meta:XML in item.metadata) 
				{
					if (meta.@name == 'Serializable')
					{
						_SERIALIZABLE_FIELDS.push(new SerializableField(item.@name, getDefinitionByName(meta.arg.(@key == 'type').@value) as Class));
					}
				}
			}
			
			_SERIALIZABLE_FIELDS.sort(fieldCompareFunction);
		}
		
		private function fieldCompareFunction(a:SerializableField, b:SerializableField):int
		{
			if (a.propName < b.propName) return -1;
			if (a.propName > b.propName) return 1;
			return 0;
		}
		
		public function serialize():ByteArray
		{
			var i:uint, j:uint, num:uint;
			var propName:String;
			var propType:Class, vecType:Class;
			var vec:*
			var b:ByteArray;
			
			_BYTE_ARRAY.length = 0;
			
			var numFields:uint = _SERIALIZABLE_FIELDS.length;
			for (i = 0; i < numFields; i++) 
			{
				propName = _SERIALIZABLE_FIELDS[i].propName;
				propType = _SERIALIZABLE_FIELDS[i].propType;
				
				if (isVector(propType))
				{
					if (this[propName])
					{
						vec = this[propName];
						num = vec.length;
						
						_BYTE_ARRAY.writeUnsignedInt(num);
						
						vecType = getVectorType(propType);
						
						switch (vecType) 
						{
							case Boolean:
								for (j = 0; j < num; j++) _BYTE_ARRAY.writeBoolean(vec[j]);
								break;
							case byte:
							case ubyte:
								for (j = 0; j < num; j++) _BYTE_ARRAY.writeByte(vec[j]);
								break;
							case double:
								for (j = 0; j < num; j++) _BYTE_ARRAY.writeDouble(vec[j]);
								break;
							case float:
								for (j = 0; j < num; j++) _BYTE_ARRAY.writeFloat(vec[j]);
								break;
							case int:
								for (j = 0; j < num; j++)
								{
									_BYTE_ARRAY.writeInt(vec[j]);
								}
								break;
							case short:
							case ushort:
								for (j = 0; j < num; j++)
								{
									_BYTE_ARRAY.writeShort(vec[j]);
								}
								break;
							case String:
								for (j = 0; j < num; j++) _BYTE_ARRAY.writeUTF(vec[j]);
								break;
							case uint:
								for (j = 0; j < num; j++) _BYTE_ARRAY.writeUnsignedInt(vec[j]);
								break;
							default:
								for (j = 0; j < num; j++) 
								{
									b = (vec[j] as SerializableObject).serialize();
									_BYTE_ARRAY.writeUnsignedInt(b.length);
									_BYTE_ARRAY.writeBytes(b);
								}
						}
					}
					else
					{
						_BYTE_ARRAY.writeUnsignedInt(0);
					}
				}
				else
				{
					switch (propType) 
					{
						case Boolean:
							_BYTE_ARRAY.writeBoolean(this[propName]);
							break;
						case byte:
						case ubyte:
							_BYTE_ARRAY.writeByte(this[propName]);
							break;
						case double:
							_BYTE_ARRAY.writeDouble(this[propName]);
							break;
						case float:
							_BYTE_ARRAY.writeFloat(this[propName]);
							break;
						case int:
							_BYTE_ARRAY.writeInt(this[propName]);
							break;
						case short:
						case ushort:
							_BYTE_ARRAY.writeShort(this[propName]);
							break;
						case String:
							_BYTE_ARRAY.writeUTF(this[propName]);
							break;
						case uint:
							_BYTE_ARRAY.writeUnsignedInt(this[propName]);
							break;
						default:
							if (this[propName])
							{
								b = (this[propName] as SerializableObject).serialize();
								_BYTE_ARRAY.writeUnsignedInt(b.length);
								_BYTE_ARRAY.writeBytes(b);
							}
							else
							{
								_BYTE_ARRAY.writeUnsignedInt(0);
							}
					}
				}
			}
			
			_BYTE_ARRAY;
			
			return _BYTE_ARRAY;
		}
		
		public function deserialize(ba:ByteArray, offset:uint = 0):void
		{
			var i:uint, j:uint, num:uint, objLen:uint, offset:uint;
			var propName:String;
			var propType:Class, vecType:Class;
			var vec:*, obj:*;
			var b:ByteArray;
			
			ba.position = offset;
			
			var numFields:uint = _SERIALIZABLE_FIELDS.length;
			for (i = 0; i < numFields; i++) 
			{
				propName = _SERIALIZABLE_FIELDS[i].propName;
				propType = _SERIALIZABLE_FIELDS[i].propType;
				
				if (isVector(propType))
				{
					num = ba.readUnsignedInt();
					
					if (num == 0)
					{
						this[propName] = null;
					}
					else
					{
						vecType = getVectorType(propType);
						
						if (vecType == byte || vecType == short)
						{
							vec = new Vector.<int>();
						}
						else if (vecType == ubyte || vecType == ushort)
						{
							vec = new Vector.<uint>();
						}
						else if (vecType == double || vecType == float)
						{
							vec = new Vector.<Number>();
						}
						else
						{
							vec = new propType();
						}
						
						switch (vecType) 
						{
							case Boolean:
								for (j = 0; j < num; j++) vec.push(ba.readBoolean());
								this[propName] = vec;
								break;
							case byte:
								for (j = 0; j < num; j++) vec.push(ba.readByte());
								this[propName] = vec;
								break;
							case double:
								for (j = 0; j < num; j++) vec.push(ba.readDouble());
								this[propName] = vec;
								break;
							case float:
								for (j = 0; j < num; j++) vec.push(ba.readFloat());
								this[propName] = vec;
								break;
							case int:
								for (j = 0; j < num; j++) vec.push(ba.readInt());
								this[propName] = vec;
								break;
							case short:
								for (j = 0; j < num; j++) vec.push(ba.readShort());
								this[propName] = vec;
								break;
							case String:
								for (j = 0; j < num; j++) vec.push(ba.readUTF());
								this[propName] = vec;
								break;
							case ubyte:
								for (j = 0; j < num; j++) vec.push(ba.readUnsignedByte());
								this[propName] = vec;
								break;
							case uint:
								for (j = 0; j < num; j++) vec.push(ba.readUnsignedInt());
								this[propName] = vec;
								break;
							case ushort:
								for (j = 0; j < num; j++) vec.push(ba.readUnsignedShort());
								this[propName] = vec;
								break;
							default:
								for (j = 0; j < num; j++) 
								{
									b = new ByteArray();
									objLen = ba.readUnsignedInt();
									offset = ba.position;
									ba.readBytes(b, offset, objLen);
									
									obj = new vecType() as SerializableObject;
									obj.deserialize(b, offset);
									
									vec.push(obj);
								}
								
							this[propName] = vec;
						}
					}
				}
				else
				{
					switch (propType) 
					{
						case Boolean:
							this[propName] = ba.readBoolean();
							break;
						case byte:
							this[propName] = ba.readByte();
							break;
						case double:
							this[propName] = ba.readDouble();
							break;
						case float:
							this[propName] = ba.readFloat();
							break;
						case int:
							this[propName] = ba.readInt();
							break;
						case short:
							this[propName] = ba.readShort();
							break;
						case String:
							this[propName] = ba.readUTF();
							break;
						case ubyte:
							this[propName] = ba.readUnsignedByte();
							break;
						case uint:
							this[propName] = ba.readUnsignedInt();
							break;
						case ushort:
							this[propName] = ba.readUnsignedShort();
							break;
						default:
							objLen = ba.readUnsignedInt();
							
							if (objLen == 0)
							{
								this[propName] = null;
							}
							else
							{
								b = new ByteArray();
								
								offset = ba.position;
								ba.readBytes(b, offset, objLen);
								
								obj = new propType() as SerializableObject;
								obj.deserialize(b, offset);
								
								this[propName] = obj;
							}
					}
				}
			}
		}
		
		public function toString():String
		{
			var i:int;
			var propName:String;
			var propType:Class;
			
			var s:String = this['constructor'] + ':{';
			
			var numFields:uint = _SERIALIZABLE_FIELDS.length;
			for (i = 0; i < numFields; i++) 
			{
				if (i > 0) s += ', ';
				
				propName = _SERIALIZABLE_FIELDS[i].propName;
				propType = _SERIALIZABLE_FIELDS[i].propType;
				
				if (isVector(propType))
				{
					s += propName + '=<' + this[propName] + '>';
				}
				else
				{
					s += propName + '=' + this[propName];
				}
			}
			
			s += '}';
			
			return s;
		}
		
		private function isVector(type:Class):Boolean
		{
			return VECTOR_PATTERN.exec(getQualifiedClassName(type)) != null;
		}
		
		private function getVectorType(vec:Class):Class
		{
			var vecStr:String = getQualifiedClassName(vec);
			var str:String = VECTOR_TYPE_PATTERN.exec(vecStr)[1];
			
			return Class(getDefinitionByName(str));
		}
	}
}

internal class SerializableField
{
	public var propName:String;
	public var propType:Class;
	
	public function SerializableField(propName:String, propType:Class)
	{
		this.propName = propName;
		this.propType = propType;
	}
}